import re
from copy import copy, deepcopy
from typing import (List, Union, Mapping, Dict, Sequence, Callable, TypeVar,
                    Iterable, Tuple)

from parsel import Selector
from abc import ABCMeta

from .log import logger

T = TypeVar('T')


class Tag(metaclass=ABCMeta):

    def __init__(self, text: str = '', attr: Mapping = None) -> None:
        """

        Args:
            text: 内容
            attr: 标签 html 属性
        """
        self._text = text
        self._attr = attr if attr else dict()

    def __str__(self) -> str:
        return f"<{self.name}{self.attr_str}>{self.text}</{self.name}>"

    @property
    def name(self) -> str:
        """
        返回标签名
        Returns:

        """
        return str(self.__class__.__name__).lower()

    @property
    def attr(self) -> Dict:
        return self._attr

    @attr.setter
    def attr(self, v: Mapping) -> None:
        self._attr.update(v)

    @attr.deleter
    def attr(self) -> None:
        self._attr = dict()

    @property
    def attr_str(self) -> str:
        if self.attr:
            ret_str = ' '.join(
                [f"{_k}=\"{_v}\"" for _k, _v in self.attr.items()])
            return f" {ret_str}"
        else:
            return ''

    @property
    def text(self) -> str:
        return str(self._text)

    @text.setter
    def text(self, v: str) -> None:
        self._text = str(v)

    @classmethod
    def get_attr(cls, selector: Selector, attr: Mapping = None) -> Dict:
        """
        获取节点的属性字典
        Args:
            selector:
            attr: 替换属性

        Returns:

        """
        _tag = cls.__name__.lower()

        # 获取属性名
        _keys = re.findall(
            r'(\w+)=["\']?.*?["\']?',
            selector.re(f"<{_tag}.*?>")[0]
        )
        # 获取属性值
        ret_dict = {_k: selector.xpath(f"./@{_k}").extract_first()
                    for _k in _keys}
        if attr:
            ret_dict.update(attr)
        return ret_dict

    @classmethod
    def get_selector(cls, html: str) -> Selector:
        """
        获取节点选择器 属性字典
        Args:
            html:

        Returns:

        """
        _tag = cls.__name__.lower()  # 标签名
        # 取第一个符合的标签 避免内容包含相同标签
        selector = Selector(text=html).xpath(f"//{_tag}")
        if len(selector) < 1:
            logger.warning(f"没有标签：{_tag}，内容：{html}")
            return Selector(text='')
        else:
            return selector[0]

    @classmethod
    def from_html(cls: T, html: str, attr: Mapping = None) -> T:
        """
        html 转 对象
        Args:
            html: html 字符串
            attr: 替换属性

        Returns:

        """
        html = re.sub(r'[\r\n]', '', html)
        _selector = cls.get_selector(html)
        _texts = _selector.xpath(r'.//text()').extract()  # 文本内容
        _texts = [re.sub(r',', '，', _t) for _t in _texts]  # 替换连接符字符串
        return cls(
            text=','.join(_texts),
            attr=cls.get_attr(_selector, attr)
        )

    def _attr_del(self, key: str) -> None:
        """
        属性统一删除方法
        Returns:

        """
        if key in self.attr:
            del self._attr[key]

    def copy(self: T, is_deep: bool = True) -> T:
        if is_deep:
            return deepcopy(self)
        else:
            return copy(self)

    def print_tag(self, indent: int = 0, end='\n') -> None:
        """
        打印标签名
        Args:
            indent: 缩进数
            end: 结尾

        Returns:

        """
        print(f"{' ' * indent}{self.name}", end=end)


class TD(Tag):
    @property
    def col(self) -> int:
        return int(self.attr.get('colspan', 1))

    @col.setter
    def col(self, v: int) -> None:
        self.attr = {'colspan': v}

    @col.deleter
    def col(self) -> None:
        self._attr_del('colspan')

    @property
    def row(self) -> int:
        return int(self.attr.get('rowspan', 1))

    @row.setter
    def row(self, v: int) -> None:
        self.attr = {'rowspan': v}

    @row.deleter
    def row(self) -> None:
        self._attr_del('rowspan')


class TH(TD):
    pass


class Tags(Tag):
    _log_record = ''
    nodes = ()  # 节点对象序列
    # 获取子节点的规则
    child_rule = r''
    # 子标签结尾-节点类映射
    child_end_tag_mapping = {}

    def __init__(self, children: Sequence[Tag], *args, **kwargs):
        # 子节点集合
        self._children = list(children) if not None else []
        super(Tags, self).__init__(text=self.text, *args, **kwargs)

    def __len__(self) -> int:
        return len(self._children)

    def __iter__(self) -> Iterable:
        return iter(self._children)

    def __getitem__(self: T, item: Union[int, slice]) -> Union[T, TD, TH]:
        if isinstance(item, slice):
            return self.__class__(self._children[item], attr=self.attr)
        elif isinstance(item, int):
            return self._children[item]

    def __reversed__(self) -> None:
        self._children.reverse()

    def __add__(self: T, other: 'Tags') -> T:
        self._children += other.children
        return self

    @property
    def children(self) -> List:
        return list(self._children)

    @children.setter
    def children(self, v: Sequence) -> None:
        self._children = list(v)

    @property
    def to_str(self) -> str:
        """
        所有节点内容拼接
        Returns:

        """
        return ''.join([str(_c) for _c in self._children])

    @property
    def to_text(self) -> List[str]:
        """
        所有节点内容
        Returns:

        """
        return [_td.text for _td in self._children]

    @property
    def text(self) -> str:
        """
        重写 text 属性
        Returns:

        """
        return self.to_str

    @property
    def array_3d(self) -> List:
        if isinstance(self, TR):
            return self.to_text
        else:
            return [_i.array_3d for _i in self]

    @property
    def matrix(self) -> List:
        return [_j for _i in self.array_3d for _j in _i]

    @classmethod
    def from_html(cls, html: str, attr: Mapping = None) -> 'Tags':
        """
        html 转节点对象
        Args:
            html: 代码字符串
            attr: 节点属性

        Returns:

        """
        html = re.sub(r'[\r\n]', '', html)
        _selector = cls.get_selector(html)
        children = []
        # 获取子节点对象序列
        _nodes = cls.nodes  # type: Tuple
        if not _nodes:
            logger.warning(f"类属性: nodes(子节点序列) 未被定义")
        # 获取子节点规则
        _rule = cls.child_rule  # type: re
        if not _rule:
            _rule = '|'.join(f"./{_n.__name__.lower()}" for _n in _nodes)
        # logger.info(f"子节点获取规则：{_rule}")
        # 获取节点标签类映射
        _mapping = cls.child_end_tag_mapping  # type: Dict
        if not _mapping:
            _mapping = {f"</{_n.__name__.lower()}>": _n for _n in _nodes}
        _mapping_str = ', '.join(
            [f"'{_k}': {_c.__name__}" for _k, _c in _mapping.items()])
        # logger.info(f"节点类映射：{_mapping_str}")
        # 遍历 行
        for _child in _selector.xpath(_rule):  # type: Selector
            _child_str = _child.extract().strip()  # 获取匹配的字符串
            _class = None  # 默认值
            # 便签结尾对应类对象
            for _k, _c in _mapping.items():
                if _child_str.endswith(_k):
                    _class = _c
            if _class:
                _html = _child.extract().strip()  # 子节点 html
                if _class in (TD, TH):
                    _msg = f"子节点 html: {_html}"
                    if cls._log_record != _msg:
                        cls._log_record = _msg
                        logger.info(_msg)
                children.append(_class.from_html(_html))
        return cls(children, attr=cls.get_attr(_selector, attr))

    def each(self: T, call_back: Callable) -> T:
        """
        遍历
        Args:
            call_back: 回调函数

        Returns:

        """
        for _c in self:
            if (ret := call_back(copy(_c))) is not None:
                _c = ret
        return self


class TR(Tags):
    nodes = (TH, TD)

    def __init__(self, children: Sequence = (), *args, **kwargs):
        # 子节点集合
        children = [TD(_c) if isinstance(_c, str) else _c for _c in children]
        super(TR, self).__init__(children, *args, **kwargs)

    def split(self: T) -> T:
        """
        拆分合并列单元格
        :return:
        """
        children = self.children  # type: List # 获取数据集
        _insert_num = 0  # 插值数量统计
        for _i, _child in enumerate(self.copy()):  # type: int, Union[TD, TH]
            # 判断是否是合并列单元格
            if (_c := _child.col) > 1:
                del self[_i].col  # 清除合并 列 属性
                # 拆分合并单元列
                for _ in range(_c - 1):
                    # 插入单元格
                    children.insert(_i + 1 + _insert_num, self[_i].copy())
                    _insert_num += 1
        self.children = children  # 数据回填
        return self

    def print(self: T, end: str = ' ', indent: int = 0) -> T:
        """
        打印节点
        Args:
            end: 结尾字符
            indent: 缩进

        Returns:

        """
        self.print_tag(indent=indent, end='')
        print(f"len:{len(self)} | ", end='')
        for _i, _c in enumerate(self):  # type: int, Union[TH, TD]
            print(f"{_c.name}:'{_c.text}'", end='')
            if _i < len(self) - 1:
                print(end, end='')
        print()
        return self


class TBody(Tags):
    nodes = (TR,)

    def __init__(self, children: Sequence[Union[Sequence, TR]] = (),
                 *args, **kwargs):
        children = [_c if isinstance(_c, TR) else TR(_c) for _c in children]
        super(TBody, self).__init__(children, *args, **kwargs)

    def split(self: T) -> T:
        """
        拆分合并行单元格
        :return:
        """
        # 遍历所有行
        for _i in range(len(self)):  # type: int
            _children = self[_i]  # 获取行数据
            _children.split()  # 拆分合并列
            # 遍历所有单元格
            for _j, _child in enumerate(
                    _children.copy()):  # type: int, Union[TD,TH]
                if (_r := _child.row) > 1:
                    del _children[_j].row  # 清除合并 行 属性
                    for _k in range(_r - 1):
                        _index = _i + _k + 1  # 行号
                        if _index >= len(self):
                            continue
                        # 获取行数据集
                        children_sub = self[_index].children  # type: List
                        children_sub.insert(_j, _children[_j].copy())
                        self[_index].children = children_sub  # 回填
        return self

    def print(self: T, indent: int = 0) -> T:
        """
        打印节点
        Args:
            indent: 缩进

        Returns:

        """
        self.print_tag(indent=indent, end=':\n')
        self.each(lambda c: c.print(', ', indent + 2))
        return self


class THead(TBody):
    pass


class TFoot(TBody):
    pass


class Table(Tags):
    nodes = (THead, TBody, TFoot, TR)

    def __init__(self, children: Sequence[Union[THead, TBody, TFoot, Sequence]],
                 *args, **kwargs):
        children = list(children)  # 强制转换类型
        # 定义容器
        _thead = THead()  # 头
        _tbody = TBody()  # 主体
        _tfoot = TFoot()  # 脚
        # 处理普通序列
        for _i, _child in enumerate(copy(children)):
            _class = THead if _i == 0 else TBody
            if isinstance(_child, Sequence):
                children[_i] = _class(_child)
            elif isinstance(_child, TR):
                children[_i] = TBody([_child])
        # 合并同类型
        for _child in children:
            # tbody 必须在最后，否则会拦截 tfoot 和 thead
            if isinstance(_child, THead):
                _thead += _child
            elif isinstance(_child, TFoot):
                _tfoot += _child
            elif isinstance(_child, TBody):
                _tbody += _child
            else:
                pass

        super(Table, self).__init__(
            [_t for _t in [_thead, _tbody, _tfoot] if len(_t) > 0],
            *args, **kwargs)

    def split(self) -> 'Table':
        """
        拆分合并单元格
        Returns:

        """
        for _c in self:
            _c.split()
        return self

    def print(self, indent: int = 0) -> 'Table':
        """
        打印数据
        Args:
            indent: 缩进数

        Returns:

        """
        self.print_tag(indent=indent, end=':\n')
        self.each(lambda c: c.print(indent=2))
        return self
